#pragma once
#include<iostream>
#include<atomic>
#include<stdlib.h>
#include"Util.hpp"
#include<jsoncpp/json/json.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<sys/resource.h>
#include<signal.h>

class Compile{
  public:
    static std::unordered_map<int, std::string> statusMap;
    static std::atomic<size_t> sourid;/*用于标记文件名唯一性,之后改成时间戳+Userid*/
    /*1.构建源文件路径*/
    inline static std::string SourceFile(const std::string filename) {
        return "./tempFiles/" + filename + std::to_string(sourid) + ".cc";
    }
    /*2.构建stdin路径*/
    inline static std::string StdinFile(const std::string filename) {
        return "./tempFiles/" + filename + std::to_string(sourid) + ".stdin";
    }
    /*3.构建可执行程序路径*/
    inline static std::string ExecFile(const std::string filename) {
        return "./tempFiles/" + filename + std::to_string(sourid) + ".out";
    }
    /*4.构建stdout路径*/
    inline static std::string StdoutFile(const std::string filename) {
        return "./tempFiles/" + filename + std::to_string(sourid) + ".stdout";
    }
    /*5.构建stderr路径*/
    inline static std::string StderrFile(const std::string filename) {
        return "./tempFiles/" + filename + std::to_string(sourid) + ".stderr";
    }
    /*6.存放输入数据的路径*/
    inline static std::string DataFileIn(const std::string filename, const std::string id) {
        return "./DataTest/" + filename + "/test" + id + ".in"; 
    }
    /*7.存放输出数据的路径*/
    inline static std::string DataFileOut(const std::string ProblemID, const std::string id) {
        return "./DataTest/" + ProblemID + "/test" + id + ".out"; 
    }
    /*判断运行结果和正确答案是否一致*/
    static bool Differ(const std::string& filename1, const std::string& filename2) {
           std::string diffcommand = "diff ";
           diffcommand += filename1;
           diffcommand += " ";
           diffcommand += filename2;
           diffcommand += " ";
           diffcommand += "-q";
           if (system(diffcommand.c_str()) != 0) {
              LOG(INFO, "文件内容不同");
              return false;
           }         
           else { /**/
              LOG(INFO, "文件内容相同");
              return true;
           }
    }
    static bool CompileCode(const Json::Value& js_req, const std::string& filename) {
        /*源文件名称先填"",留着*/
        LOG(INFO, "保留源文件: " + SourceFile(filename));
        FileUtil::WriteFile(SourceFile(filename), js_req["code"].asString());
        int pid = fork(); 
        if (pid == 0) { /*子进程,进程程序替换,调用g++进行编译*/
            int fd = open(StderrFile(filename).c_str(), O_CREAT | O_RDWR, 0666);
            LOG(INFO, "fd =" + std::to_string(fd));
            dup2(fd, 2);/*重定向标准错误流,输出到文件*/
            LOG(INFO, "g++ -o " + ExecFile(filename) + "  " +  SourceFile(filename));
            if (execlp("g++", "g++", "-o", ExecFile(filename).c_str(), SourceFile(filename).c_str(), NULL) < 0) {
                perror("execlp failed");
                LOG(FATAL, "execlp failed");
                exit(2);
            }             
        }
        else if(pid > 0) { /*父进程*/
            waitpid(pid, nullptr, 0);
            struct stat _stat;
            /*文件存在*/
            if (stat(ExecFile(filename).c_str(), &_stat) == 0) {
              return true;
            }
            else return false;/*这里不用关心子进程的状态,通过文件是否成功创建去判定编译是否成功*/
        }
        else {
            LOG(FATAL, "fork failed");
            return false;
        }
        return true;
    }
    /*先判断有无在黑名单中的内容*/
    static bool CheckCode(const Json::Value& js_req) {
            bool ret = SecurityUtil::FindKeyWord(js_req);
            if (ret > 0) {
                LOG(FATAL, "dangerous code!!!");
                return -1;
            }
            else return 0;
    }
    /*缺省匿名对象,实际需要获得子进程的运行时间和内存占用才会去传参*/
    static int RunCode(const std::string& TimeFilefilename, const std::string& DataFileInfilename, const int& testid, Json::Value& js_res) {
            int pid = fork();
            if (pid == 0) { /*子进程*/
                int fd1 = open(StdoutFile(TimeFilefilename).c_str(), O_CREAT | O_RDWR | O_TRUNC, 0666);
                int fd2 = open(DataFileIn(DataFileInfilename, std::to_string(testid)).c_str(), O_CREAT | O_RDWR, 0666);
                LOG(INFO, ExecFile(TimeFilefilename));
                dup2(fd1, 1);
                dup2(fd2, 0);
                /*对子进程的限制操作*/
                alarm(5);/*限制进程只跑5s*/
                int nMemoryLimit = 1024 * 1024 * 30;/*30M*/
                struct rlimit memoryL;
                getrlimit(RLIMIT_AS, &memoryL);
               // printf("before set mem:%lu,%lu\r\n", (unsigned long)memoryL.rlim_cur, (unsigned long)memoryL.rlim_max);
                memoryL.rlim_cur = nMemoryLimit;
                setrlimit(RLIMIT_AS, &memoryL);

                getrlimit(RLIMIT_AS, &memoryL);
              //  printf("after set mem:%lu,%lu\r\n", (unsigned long)memoryL.rlim_cur, (unsigned long)memoryL.rlim_max);

                if (execlp((ExecFile(TimeFilefilename)).c_str(), (ExecFile(TimeFilefilename)).c_str(), NULL) < 0) {
                   perror("execlp failed");
                   LOG(FATAL, "execlp failed");
                   exit(2);
               }
            }
            else if (pid > 0) { /*父进程*/
                int status = 0;
                struct rusage rused;
                //waitpid(pid, &status, 0);
                if (wait4(pid, &status, 0, &rused) < 0) {
                    LOG(INFO, "wait4 failed");
                    return false;
                }
                LOG(INFO, "status =" + std::to_string(status));
                /*统计子进程运行状态*/
                if (WIFEXITED(status)) { /*正常退出*/
                     int time_usage = (rused.ru_utime.tv_sec * 1000 + rused.ru_utime.tv_usec / 1000);
                     time_usage += ( rused.ru_stime.tv_sec * 1000 + rused.ru_stime.tv_usec / 1000 );
                     int memory_usage = rused.ru_minflt * ( getpagesize() / 1024 );
                     LOG(INFO, "Time used: " + std::to_string(time_usage) + "MS Memory used: " + std::to_string(memory_usage) + "KB");
                     js_res["time" + std::to_string(testid)] = std::to_string(time_usage);
                     js_res["memory" + std::to_string(testid)] = std::to_string(memory_usage);
                     return WEXITSTATUS(status);
                }
                else if(WIFSIGNALED(status)){ /*收到信号退出*/
                     return WTERMSIG(status);/*获得信号状态*/
                }
            }
            else {
                LOG(FATAL, "fork failed");  
                return true;
            }
            return false;
    }
    /*编译运行部分*/
    static bool CompileAndRun(const Json::Value& js_req, Json::Value& js_res) {
        /*保存源代码到文件中*/
        if (js_req["code"].asString().empty()) {
            return false;
        } 
        std::string filename = std::to_string(Timemap()); /*时间戳保存临时文件*/
        js_res["filepath"] = SourceFile(filename);/*保存源码的位置*/
        LOG(INFO, js_res["filepath"].asString());
        /*保存源码,创建进程，替换进程，调用g++进行编译*/
        int ret = CompileCode(js_req, filename); 
        LOG(INFO, "ret = " + std::to_string(ret));
        if (!ret) { /*编译失败,返回编译信息*/
            std::string stderrstr;
            FileUtil::ReadFile(StderrFile(filename), stderrstr); /*读取编译错误信息*/
            js_res["status"] = statusMap[4];
            js_res["reason"] = "";/*给异常用的信息*/ 
            js_res["stdout"] = "";
            js_res["stderr"] = stderrstr;
            sourid++;
            return false;
        }
        /*创建进程，替换进程，运行可执行程序*/
        ret = CheckCode(js_req); /*检查程序是否有恶意代码*/
        LOG(INFO, "ret = " + std::to_string(ret));
        if (ret >= 0) {
             /*这边还要检测一个个结果,将测试用例都放到一个.cpp文件，利用生成对象来检测比较方便(核心模式)或者acm模式,测试数据来输入输出对照*/
             int testnum = js_req["TestNum"].asInt();
             LOG(INFO, "testnum = " + std::to_string(testnum));
             /*进行每个测试对比*/
             /*正常运行结果*/
             /*读取运行结果,构建json*/
             int result = 0;/*最终结果,后续添加时间限制和空间限制:-1--Hack Code;0--Accept;1--Wrong Answer;14--Time Limit Exceeded;3--Memory Limit Exceeded;4--Compile Error;*/
             size_t totaltime = 0; 
             size_t totalmemory = 0;
             for (int i = 1; i <= testnum; i++) {
               int cnt = RunCode(filename, js_req["ProblemID"].asString(), i, js_res);
               LOG(INFO, "running test" + std::to_string(i));
               LOG(INFO, "RunCode is " + std::to_string(cnt));
               /*运行的进程被信号中断了*/
               js_res["TestNum"] = i;/*实际测试到的文件个数*/
               if (cnt > 0) {
                   if (!statusMap.count(cnt)) js_res["status"] = "Run Error";
                   else js_res["status"] = statusMap[cnt];
                   js_res["reason"] = std::to_string(cnt); 
                   std::string strstdout;
                   LOG(INFO, StdoutFile(filename));
                   FileUtil::ReadFile(StdoutFile(filename), strstdout);
                   LOG(INFO, "readfile:" + strstdout);
                   js_res["stdout"] = strstdout;
                   js_res["stderr"] = "";
                   sourid++;
                   return false;
               }
               else { /*本次结果正常,需要对照测试集合看结果是否正确*/
                   bool different = Differ(StdoutFile(filename), DataFileOut(js_req["ProblemID"].asString(), std::to_string(i)));
                   LOG(INFO, "running test" + std::to_string(i));
                   if (different == 1) { /*文件内容相同*/
                      LOG(INFO, "running test" + std::to_string(i));
                      js_res["test" + std::to_string(i)] = "Accept";
                      LOG(INFO, "running test" + std::to_string(i));
                   }
                   else { /*文件内容不同*/
                      js_res["test" + std::to_string(i)] = "Wrong Answer"; 
                      result = 1;
                   }
                   //LOG(INFO, "running test" + std::to_string(i));
                   LOG(INFO, js_res["time" + std::to_string(i)].asString());
                   /*开源库的asint似乎有问题*/
                   // std::cerr << js_res["time" + std::to_string(i)].asUInt() << std::endl;
                   std::string thistime = js_res["time" + std::to_string(i)].asString();
                   std::string thismemory = js_res["memory" + std::to_string(i)].asString();
                   totaltime += atoi(thistime.c_str());
                   totalmemory += atoi(thismemory.c_str());
                   //LOG(INFO, "running test" + std::to_string(i));
               }
             }
             /*规定时间规定内存范围内执行出结果(WA/AC)*/
             js_res["status"] = Compile::statusMap[result];
             js_res["totaltime"] = std::to_string(totaltime) + "MS";
             js_res["totalmemory"] = std::to_string(totalmemory) + "KB";
             js_res["reason"] = "";
             js_res["stdout"] = "";
             js_res["stderr"] = "";
             LOG(INFO, "create json request");
             LOG(INFO, "status:" + js_res["status"].asString());
             LOG(INFO, "reason:" + js_res["reason"].asString());
             LOG(INFO, "stdout:" + js_res["stdout"].asString());
             LOG(INFO, "stderr:" + js_res["stderr"].asString());
             sourid ++;
             return true;
        }
        else { /*ret == -1 危险代码*/
            js_res["status"] = statusMap[-1];
            js_res["reason"] = "dangerous code";
            js_res["stdout"] = "";
            js_res["stderr"] = "";
            sourid ++;
            return false;
        }
        return true;
    }
};
/*响应报文*/
/*status:最终结果,后续添加时间限制和空间限制:-1--Dangerous Code;0--Accept;1--Wrong Answer;14--Time Limit Exceeded;3--Memory Limit Exceeded;4--Compile Error*/
/*reason:错误码*/
/*stdout*/
/*stderr*/
std::atomic<size_t> Compile::sourid(0);
std::unordered_map<int, std::string> Compile::statusMap = {{-1, "Hack Code"}, {0, "Accept"}, {1, "Wrong Answer"}, {14, "Time Limit Exceeded"}, {3, "Memory Limit Exceeded"}, {4, "Compile Error"}};
